package com.crazy.cloud.guid.biz.service.impl;

import com.crazy.cloud.common.api.ApiResponse;
import com.crazy.cloud.guid.api.GuidRequestDTO;
import com.crazy.cloud.guid.api.GuidService;
import com.crazy.cloud.guid.domain.model.Guid;
import com.crazy.cloud.guid.domain.repository.GuidRepository;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @program: crazy-cloud
 * @description: 唯一ID生成
 * @author: CrazyMan
 * @create: 2018/9/27 下午8:39
 **/
@Slf4j
@RestController
@RequestMapping("/guid")
public class GuidServiceImpl implements GuidService {

    @Autowired
    private GuidRepository guidRepository;

    ConcurrentHashMap<String, AtomicLong> currentIdMap = new ConcurrentHashMap<>();
    ConcurrentHashMap<String, AtomicLong> maxIdMap = new ConcurrentHashMap<>();


    @Override
    @PostMapping
    public ApiResponse<Long> nextId(@RequestBody GuidRequestDTO request) {
        log.info("开始 {}", request);
        try {
            String bizType = request.getBizType();
            AtomicLong currentId = currentIdMap.get(bizType);
            AtomicLong maxId = maxIdMap.get(bizType);
            if (currentId == null || maxId == null) {
                currentId = new AtomicLong();
                maxId = new AtomicLong();
                currentIdMap.put(bizType, currentId);
                maxIdMap.put(bizType, maxId);
            }
            if (currentId.longValue() == maxId.longValue()) {
                synchronized (currentId) {
                    if (currentId.longValue() == maxId.longValue()) {
                        long maxIdNew;
                        long currentIdNew;
                        int row;
                        int i = 0;
                        do {
                            Guid guid = guidRepository.selectNext(bizType);
                            if (null == guid) {
                                return ApiResponse.error(40404, "nextId failed:bizType not in mysql");
                            }
                            long lowerLimit = guid.getLowerLimit();
                            long upperLimit = guid.getUpperLimit();
                            long currentIdDb = guid.getCurrentId();
                            int step = guid.getStep();
                            long maxIdTmp = currentIdDb + step;

                            if (lowerLimit + step >= upperLimit || lowerLimit >= upperLimit) {
                                return ApiResponse.error("nextId failed:lowerLimit + step >=upperLimit");
                            }
                            currentIdNew = currentIdDb;
                            if (currentIdDb < lowerLimit || currentIdDb > upperLimit || currentIdDb == upperLimit || currentIdDb == lowerLimit) {
                                maxIdTmp = lowerLimit + step;
                                currentIdNew = lowerLimit - 1;
                            } else if (maxIdTmp > upperLimit) {
                                maxIdTmp = upperLimit;
                            }
                            row = guidRepository.updateNext(bizType, currentIdDb, maxIdTmp);
                            maxIdNew = maxIdTmp;
                            i++;
                        } while (row == 0 && i < 3);
                        if (i >= 3) {
                            return ApiResponse.error("nextId failed:too much times");
                        }
                        maxId.set(maxIdNew);
                        currentId.set(currentIdNew);
                    }
                }
            }
            long result = currentId.addAndGet(1);
            log.info("currentId:{}", result);
            return new ApiResponse<>(result);
        } catch (Exception ex) {
            log.error("nextId exception", ex);
            return ApiResponse.error("nextId failed");
        }

    }
}
